package za.ac.wits.elen7045.persistence.dao;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import za.ac.wits.elen7045.persistence.Environment;
import za.ac.wits.elen7045.persistence.datasource.DataSource;
import za.ac.wits.elen7045.persistence.datasource.DataSourceFactory;
import za.ac.wits.elen7045.persistence.encryption.EncryptorFactory;
import za.ac.wits.elen7045.persistence.exception.DaoException;
import za.ac.wits.elen7045.persistence.model.Account;
import za.ac.wits.elen7045.persistence.model.User;
import za.ac.wits.elen7045.persistence.util.SQLQueries;

public class SqlUserDAO implements SqlDao {

	private SqlAccountDAO accountDAO;
	private DataSource<User> userDSource;
	
	/** 
	 * Constructor allows package access ONLY to force the use of the factory 
	 * */
	SqlUserDAO(Environment env) {
		accountDAO = DaoFactory.getAccountDAO(env);
		userDSource = DataSourceFactory.getDataSource(User.class, env);
	}
	
	public boolean isAuthorised(String username, String password) {
		try {
			userDSource.open();
			String query = SQLQueries.isUserAuthorised(username, EncryptorFactory.getPasswordEncryptor().encrypt(password));
			return userDSource.retrieve(query).iterator().hasNext();
		} catch (Exception e) {
			return false;
		} 
		finally {
			userDSource.close();
		}
	}
	
	public boolean userExist(String username) {
		return getUser(username) != null;
	}
	
	public User getUser(String username) {
		try {
			userDSource.open();
			String query = SQLQueries.getUser(username);
			Iterator<User> userIterator = userDSource.retrieve(query).iterator();
			if (userIterator.hasNext()) {
				User user = userIterator.next();
				user.setAccounts(accountDAO.getAccounts(user));
				return user;
			}
			return null;
		} catch (Exception e) {
			throw new DaoException("Unable to load User (where username="+ username +") from database!");
		} 
		finally {
			userDSource.close();
		}
	}
	
	public List<User> getAllUsers() {
		try {
			userDSource.open();
			String query = SQLQueries.getAllUsers();
			List<User> users = new LinkedList<User>();
			/* get users from source */
			for (User user : userDSource.retrieve(query)) {
				users.add(user);
			}
			/* 
			 * Get associated accounts for each user: 
			 * NOTE: 
			 * 		By making another call to source inside the above for-loop may result in 
			 * 		some database and are therefore written as separated actions... 
			 */
			for (User user : users) {
				user.setAccounts(accountDAO.getAccounts(user));
			}
			return users;
		} catch (Exception e) {
			e.printStackTrace();
			throw new DaoException("Unable to load users from database!");
		} 
		finally {
			userDSource.close();
		}
	}
	
	public void addUser(User user) {
		try {
			userDSource.open();
			/* Step 1 - add new user */
			userDSource.submit(SQLQueries.addUser(user));
			/* Step 2 - get "updated" copy of user (with ID) */
			String encryptedPassword = EncryptorFactory.getPasswordEncryptor().encrypt(user.getPassword());
			user = userDSource.retrieve(SQLQueries.getUserWithoutPassword(user.getUsername())).iterator().next();
			user.setPassword(encryptedPassword);
			/* Step 3 - set user's password */
			userDSource.submit(SQLQueries.addUserPassword(user));
			userDSource.commit();
			/* Step 4 - add user's accounts */
			List<Account> accounts = user.getAccounts();
			if (accounts != null && !accounts.isEmpty()) {
				accountDAO.addAccounts(user, accounts);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new DaoException("Unable to load users from database!");
		} 
		finally {
			userDSource.close();
		}
	}
	
	public void removeUser(User user) {
		try {
			userDSource.open();
			/* Step 1 - get "updated" copy of user (with ID) */
			user = userDSource.retrieve(SQLQueries.getUser(user.getUsername())).iterator().next();
			/* Step 2 - remove user */
			userDSource.submit(SQLQueries.removeUser(user));
			/* Step 3 - remove user passwords */
			userDSource.submit(SQLQueries.addUserPassword(user));
			userDSource.commit();
			/* Step 4 - remove user's accounts */
			List<Account> accounts = user.getAccounts();
			if (accounts != null && !accounts.isEmpty()) {
				accountDAO.removeAccounts(user, accounts);
			}
		} catch (Exception e) {
			throw new DaoException("Unable to load users from database!");
		} 
		finally {
			userDSource.close();
		}
	}
	
	public void addUserPassword(User user, String newPassword) {
		try {
			userDSource.open();
			/* Step 1 - get "updated" copy of user (with ID) */
			user = userDSource.retrieve(SQLQueries.getUserWithoutPassword(user.getUsername())).iterator().next();
			user.setPassword(EncryptorFactory.getPasswordEncryptor().encrypt(newPassword));
			/* Step 2 - set user's new password */
			userDSource.submit(SQLQueries.addUserPassword(user));
			userDSource.commit();
		} catch (Exception e) {
			throw new DaoException("Unable to load users from database!");
		} 
		finally {
			userDSource.close();
		}
	}
	
}
